<?php
	
	namespace Aspic;
	
	/**
	* Standard class like PHP stdclass except that it could return null
	* when an element is not set.
	 * In addition, it is possible to tamper data getted, or setted by creating 
	 * a special getter/setter method
	 * @see Std::getVarTamperGetFunc
	*/
	Trait StdTrait {
		
		protected $varNameToPropertyNameFunc = null;
		protected $varSetMethodNameFunc = null;
		protected $varGetMethodNameFunc = null;
		
		protected $_reflection;
		
		protected $_vars = array();
		protected $_returnNullIfEmpty = true;
		
		protected $setableVarNames = array();
		
		/**
		* @param bool $returnNullValue If true, a reference to null will be returned when property is not set
		* else, a reference to the property will be returned (but it will trigger php error if try to access the value)
		*/
		protected function __construct($vars = array(), $returnNullIfEmpty = true) {
			$this->_reflection = new \ReflectionClass($this);
			$this->setVars($vars);
			$this->_returnNullIfEmpty = (bool)$returnNullIfEmpty;
			
			$this->setVarToPropertyNameFunc(function($varName) {
				return '_set_'.$varName;
			});
			
			$this->setOptionGetMethodNameFunc(function($varName) {
				return '_get_'.$varName;
			});
		}
		/**
		 * A function that take an option name as argument and return the 
		 * corresponding setter method name (does not means that method isset)
		 * @param callable $func
		 * @return string
		 */
		public function setVarSetMethodNameFunc(callable $func) {
			$this->varSetMethodNameFunc = $func;
		}
		
		public function getVarSetMethodNameFunc() {
			return $this->varSetMethodNameFunc;
		}

		/**
		 * A function that take an option name as argument and return the 
		 * corresponding getter method name (does not means that method isset)
		 * @param callable $func
		 * @return string
		 */
		public function setVarGetMethodNameFunc(callable $func) {
			$this->optionGetMethodNameFunc = $func;
		}
		
		public function getVarGetMethodNameFunc() {
			return $this->optionGetMethodNameFunc;
		}
		
		public function &__get($var) {
			$null = null;
			
			if($this->_returnNullIfEmpty) {
				if(isset($this->_vars[$var])) {
					return $this->get($var);
				}
				else {
					return $null;
				}
			}
			else {
				return $this->get($var);
			}
		}
		
		/**
		* Return a reference to a property (like getting property when returnNullValue set to false)
		*/
		public function &get($var) {
			$methodName = $this->getOptionGetMethodNameFunc($var);
			
			if ($this->_reflection->hasMethod($methodName) AND !$this->_reflection->getMethod($methodName)->isPublic()) {
				return $this->{$methodName}();
			}
			else {
				return $this->_vars[$var];
			}
		}
		
		public function set($var, $value) {
			if (count($this->setableVarNames) AND !in_array($var,  $this->setableVarNames)) {
				throw new A\Exception('Cannot set var "'.$var.'" because it in isn\'t in setableVarNames ('.implode(', ', $this->setableVarNames).')');
			}
			
			$methodName = $this->getOptionSetMethodNameFunc($var);
			
			if ($this->_reflection->hasMethod($methodName) AND !$this->_reflection->getMethod($methodName)->isPublic()) {
				return $this->{$methodName}($value);
			}
			else {
				return $this->_vars[$var] = $value;
			}
		}
		
		public function __set($var, $value) {
			$this->set($var, $value);
		}
		
		public function __isset($var) {
			return isset($this->_vars[$var]);
		}
		
		public function __unset($var) {
			unset($this->_vars[$var]);
		}
		
		protected function setVars($vars) {
			$vars = UArray::toArray($vars);
			
			$this->_vars = $vars;
		}
		
		protected function mergeVars(array $vars) {
			$vars = UArray::toArray($vars);
			
			$this->_vars = array_merge($this->_vars, $vars);
		}
		
		protected function getVars() {
			return $this->_vars;
		}
		
		/**
		* Unset given keys from the object (first arg could be an array or all functions args could be keys to remove)
		*/
		public function unsetKeys($keys) {
			if(!is_array($keys)) {
				$keys = func_get_args();
			}
			
			foreach($keys as $key) {
				if(isset($this->_vars[$key])) {
					unset($this->_vars[$key]);
				}
			}
		}
		
		/**
		* Unset all keys that are not in $keys (first arg could be an array or all functions args could be keys to remove)
		*/
		public function keepOnlyKeys($keys) {
			if(!is_array($keys)) {
				$keys = func_get_args();
			}
			
			foreach($keys as $key) {
				if(!in_array($key, $keys)) {
					unset($this->_vars[$key]);
				}
			}
		}
		
		public function offsetSet($offset, $value) {
			$this->__set($offset, $value);
		}
		
		public function offsetExists($offset) {
			return (isset($this->_vars[$offset]));
		}
		
		public function offsetUnset($offset) {
			unset($this->_vars[$offset]);
		}
		
		public function offsetGet($offset) {
			$res = $this->__get($offset); // To pass the reference problem when return is null
			
			return $res;
		}
		
		public function unserialize($data) {
			$unserialized = \unserialize($data);
			
			$this->init($unserialized['_vars'], $unserialized['_returnNullIfEmpty']);
		}
		
		public function serialize() {
			$serialized = \serialize(array(
				'_vars' => $this->getVars(), 
				'_returnNullIfEmpty' => $this->_returnNullIfTempty, 
			));
			
			return $serialized;
		}
		
		/**
		 * Define the only vars that could be set on the object 
		 * @param array $varNames An array of var names
		 * 
		 */
		protected function setSetableVars(array $varNames) {
			$this->setableVars = $varNames;
		}
		
		
	}
	
?>